package lock

import (
	"context"
	"github.com/redis/go-redis/v9"
	"time"
)

const (
	Second        int64 = 1000
	Expire              = 2
	DefaultExpire       = Expire * Second
	DefaultValue  int   = 1
)

const (
	ResultOK   = "OK"
	ResultNEXT = "NEXT"
)

// NonFairSyncLockScript 加非公平锁
const NonFairSyncLockScript = `
if redis.call("get", KEYS[1]) == ARGV[1]
then
	return redis.call("pexpire", KEYS[1], ARGV[2])
else
	return redis.call("set", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
end
`

// FairSyncLockScript 加公平锁
const FairSyncLockScript = `
if redis.call("get", KEYS[1]) == ARGV[1]
then
	redis.call("pexpire", KEYS[1], ARGV[2])
	redis.call('lpush',KEYS[2], ARGV[3])
	return ARGV[3];
else
	return redis.call("set", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])
end
`

// UnFairSyncLockScript 解锁公平锁通知下一个锁
const UnFairSyncLockScript = `
if redis.call("get", KEYS[1]) == ARGV[1]
then
	redis.call("del", KEYS[1])
end
if redis.call("llen", KEYS[2]) == 0 then
	return "OK"
end
local next = redis.call('rpop', KEYS[2]);
if redis.call('publish', next, 'next') == 0 then
	return "NEXT"
end
return  "OK"
`

// IncrementalTimeLockScript 续约补时
const IncrementalTimeLockScript = `
if redis.call("get", KEYS[1]) == ARGV[1] then
	return redis.call("pexpire", KEYS[1], ARGV[2])
else
	return 0
end
`

// UnlockScript 解锁
const UnlockScript = `
if redis.call("get", KEYS[1]) == ARGV[1] then
	return redis.call("del", KEYS[1])
else
	-- key在，但是值不对
	return 0
end
`

type SyncInterface interface {
	Lock(ctx context.Context) error
	UnLock(ctx context.Context) error
}

type Lock struct {
	client *redis.Client
	key    []string
	unCh   chan int32
}

func NewLock(client *redis.Client, key []string) *Lock {
	return &Lock{
		client: client,
		key:    key,
		unCh:   make(chan int32, 0),
	}
}

// Acquire 加锁
func (lock *Lock) Acquire(ctx context.Context, script string, args ...interface{}) (interface{}, error) {
	result, err := redis.NewScript(script).Run(ctx, lock.client, lock.key, args...).Result()
	if err != nil {
		return 0, err
	}
	return result, nil
}

// Refresh 续约锁
func (lock *Lock) Refresh(ctx context.Context, exp int64) error {
	_, err := redis.NewScript(IncrementalTimeLockScript).Run(ctx, lock.client, lock.key, DefaultValue, exp*Second).Result()
	if err != nil {
		return err
	}
	return nil
}

// Un 删除锁
func (lock *Lock) Un(ctx context.Context, script string, value int) (interface{}, error) {
	result, err := redis.NewScript(script).Run(ctx, lock.client, lock.key, value).Result()
	if err != nil {
		return nil, err
	}
	return result, nil
}

// TTL 获取锁所剩下的时间
func (lock *Lock) TTL(ctx context.Context) time.Duration {
	result, _ := lock.client.TTL(ctx, lock.key[0]).Result()
	return result
}

// Wait 排队等待
func (lock *Lock) Wait(ctx context.Context, key string) bool {
	subscribe := lock.client.Subscribe(ctx, key)
	defer subscribe.Close()
	select {
	case <-subscribe.Channel():
		return true
	case <-ctx.Done():
		return false
	}
}

// Deadline 超时时间
func (lock *Lock) Deadline(ctx context.Context) int64 {
	deadline, _ := ctx.Deadline()
	if deadline.Unix() > 0 {
		return deadline.Unix() - time.Now().Unix()
	}
	return Expire
}
